﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using MiscUtil.Conversion;
using MiscUtil.IO;

namespace TlvLib
{
    public delegate void RawPacketHandler(object sender, RawPacketReceivedEventArgs args);

    public class TlvUnpacker
    {
        private readonly string _host;
        private readonly int _port;
        private EndianBinaryReader _reader;

        private const int Headerlen = 4;
        private byte[] _headerBytes = new byte[Headerlen];
        private int _headerBytesRead = 0;
        private TcpClient _client;

        private bool _isReadingHeader = true;


        public event RawPacketHandler RawPacketReceived;

        private void RaiseRawPacketReceived(RawPacketReceivedEventArgs args)
        {
            RawPacketHandler handler = RawPacketReceived;
            if (handler != null) handler(this, args);
        }

        public TlvUnpacker(string host, int port)
        {
            _host = host;
            _port = port;
        }

        public void Start()
        {
            Connect();
        }

        private void Connect()
        {
            if (_client != null && _client.Connected)
            {
                _client.Client.Shutdown(SocketShutdown.Both);
                _client.Close();
            }

            _client = new TcpClient();
            _client.BeginConnect(_host, _port, ConnectCallback, _client);
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                _client.EndConnect(ar);
            }
            catch
            {
                System.Threading.Thread.Sleep(1000);
                Connect();
                return;
            }

            var stateObject = new StateObject();
            _client.GetStream().BeginRead(
                stateObject.Header,
                stateObject.HeaderBytesCount,
                stateObject.Header.Length - stateObject.HeaderBytesCount,
                ReadCallback, stateObject);

        }

        private void ReadCallback(IAsyncResult ar)
        {
            int bytesRead = 0;
            NetworkStream stream;
            var state = (StateObject)ar.AsyncState;
            try
            {
                stream = _client.GetStream();
                bytesRead = stream.EndRead(ar);
            }
            catch
            {
                Connect();
                return;
            }

            if (_isReadingHeader)
            {
                state.HeaderBytesCount += bytesRead;
                if (state.HeaderBytesCount == state.Header.Length)
                {
                    _isReadingHeader = false;
                    var i = BitConverter.ToInt32(state.Header, 4);
                    var j = System.Net.IPAddress.NetworkToHostOrder(i);
                    state.Value = new byte[j];
                }
            }
            else
            {
                state.ValueBytesCount += bytesRead;
                if (state.Value.Length == state.ValueBytesCount)
                {
                    var i = BitConverter.ToInt16(state.Header, 2);
                    var j = System.Net.IPAddress.NetworkToHostOrder(i);
                    var p = new RawPacket()
                                {
                                    PacketType = j,
                                    Payload = state.Value
                                };

                    try
                    {
                        RaiseRawPacketReceived(new RawPacketReceivedEventArgs(p));
                    }
                    catch { }

                    state = new StateObject();
                    _isReadingHeader = true;
                }
            }

            if (_isReadingHeader)
            {
                stream.BeginRead(
                                state.Header,
                                state.HeaderBytesCount,
                                state.Header.Length - state.HeaderBytesCount,
                                ReadCallback,
                                state);
            }
            else
            {
                stream.BeginRead(
                                state.Value,
                                state.ValueBytesCount,
                                state.Value.Length - state.ValueBytesCount,
                                ReadCallback,
                                state);
            }
        }
    }


    class StateObject
    {
        public const int HeaderBytesLen = 8;
        public int HeaderBytesCount;
        public byte[] Header = new byte[HeaderBytesLen];


        public int ValueBytesCount;
        public byte[] Value;
    }


}

